/*
-------------------------------------------------------------------
BIE is Copyright 2001-2003 Brunswick Corp.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/bie_license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
*/
package com.webdeninteractive.sbie.client;

import java.util.Timer;
import java.util.TimerTask;
import java.util.Map;
import java.util.Hashtable;
import java.util.Iterator;

import com.webdeninteractive.sbie.RemoteSystem;
import com.webdeninteractive.sbie.Client;
import com.webdeninteractive.sbie.Service;
import com.webdeninteractive.sbie.ServiceFactory;
import com.webdeninteractive.sbie.ProtocolHandler;
import com.webdeninteractive.sbie.ProtocolHandlerFactory;
import com.webdeninteractive.sbie.config.SystemConfig;
import com.webdeninteractive.sbie.config.BootConfig;
import com.webdeninteractive.sbie.config.data.ServiceData;
import com.webdeninteractive.sbie.exception.ComponentException;

import org.apache.log4j.Logger;

/**
 * Client implementation providing basic functionality.
 * <p>
 * This Client can start individual Services, or start timed Services.
 *
 * @author gfast
 * @version $Id: BasicClient.java,v 1.1.1.1 2003/05/20 16:56:49 gdf Exp $
 */
public class BasicClient implements Client {

    private Logger logger = Logger.getLogger("com.webdeninteractive.sbie.client.BasicClient");

    protected String name;
    protected boolean halted;
    protected Hashtable timers;
    protected RemoteSystem parent;
    protected ServiceFactory serviceFactory;
    protected SystemConfig config;
    protected ProtocolHandler protocolHandler;
    // We don't want to let anyone stop services until the initial set
    // of configured services have all been started (see run() ).
    protected boolean allServicesStarted;

    public BasicClient() {
        halted = true; // set to false by calling run(), true by calling halt()
        timers = new Hashtable();
        allServicesStarted = false;
    }

    /** Create a ProtocolHandler of the appropriate type for this Client. */
    void initProtocolHandler() 
        throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        // Create factory for protocolhandlers, and
        // create this Client's protocolhandler:
        String phname = config.getProtocolHandlerName( this.name );
        String phclass = config.getProtocolHandlerClassName( this.name );
        ProtocolHandlerFactory phf = new ProtocolHandlerFactory( 
            this.getParentSystem().getClassLoader() );        
        phf.register( phname, phclass );
        logger.debug( "Creating protocolHandler "+phname+"("+phclass+")" );
        protocolHandler = phf.makeProtocolHandler(phname);

    } // initProtocolHandler()

    /** Runs this Client when its Thread is started.
     *  This implementation creates a ServiceFactory, and then checks the
     *  configuration for the set of services to start (and when/how often 
     *  to run them).
     *  <p>
     *  This method doesn't return until this Client is halted.
     */
    public void run() {
        try {
            initProtocolHandler();
            timers = new Hashtable();
            serviceFactory = new ServiceFactory( parent.getClassLoader() );
            // start up the listed start-services
            halted = false;
            String[] services = config.getServicesForClient( this.name );
            for( int i=0; i<services.length; i++ ) {
                startService(services[i]);
            }
            allServicesStarted = true;
            logger.debug("Waiting...");
            // XXX is this waitloop really necessary? it seems lame...
            while( ! halted ) {
                try {
                    Thread.sleep( 10*1000 );
                } catch (InterruptedException ignored) {}
            }
        } catch ( Exception e ) {
            // Probably a bad configuration, or a incomplete update
            logger.warn( "BasicClient failed to run", e );
        }
        return; // exit this thread

    } // run()

    /** Signal this Client to stop running. */
    public void halt() {
        halted = true;
        // cancel all timers
        Iterator tit = timers.keySet().iterator();
        while( tit.hasNext() ) {
            Timer t = (Timer)timers.get( tit.next() );
            t.cancel();
        }
    }

    /** Run a given named Service, according to its configuration.
     *  If the service has a defined interval, a new timed service will be
     *  started for it, otherwise it will be run a single time.
     *  Does nothing if no service of the given name is configured 
     *  for this Client.
     * @see #runService
     * @see #startTimedService
     */
    public void startService( String serviceName ) {
        ServiceData servicedata = 
            config.getServiceDataByName( this.name, serviceName );
        if (servicedata!=null) {
            logger.info( "Starting service: " +
                         servicedata.getServiceName() + ", " +
                         servicedata.getServiceInterval() );
            if ( servicedata.getServiceInterval() == -1 ) {
                startServiceRunOnce( servicedata.getServiceName() );
            } else {
                startTimedService( servicedata.getServiceName(),
                                   servicedata.getServiceInterval() );
            }
        }

    } // startService()

    /** Run a given named Service once.
     *  @throws IllegalStateException if called while halted. 
     */
    public void runService( String serviceName ) 
        throws ComponentException, IllegalStateException
    {
        if (halted) {
            throw new IllegalStateException("Client has been halted");
        }
        String implClassName =
            config.getServiceImplClass( this.name, serviceName );
        logger.debug("Service " + serviceName + " impl class " + implClassName);
        Service service = null;
        try {
            service = serviceFactory.makeServiceByClassName( implClassName );
        } catch (Exception e) {
            // ClassNotFoundException, InstantiationException,
            // IllegalAccessException
            logger.warn( "Could not instantiate service implementation " +
                            "class " + implClassName );
            throw new ComponentException(e);
        }
        service.setConfiguration( this.config );
        service.setProtocolHandler( this.protocolHandler );
        service.setParentClient( this );
        service.setParameters( 
            this.config.getServiceParameters( this.name, serviceName ) );
        service.runService();
    }

    public void startServiceRunOnce( String serviceName )
        throws IllegalStateException
    {
        if (halted) {
            throw new IllegalStateException("Client has been halted");
        }
        Timer t = new Timer();
        t.schedule( new LocalTimerTask( serviceName ), 10 ); // token delay
        logger.info( "Service '" + serviceName + "' scheduled to run once" );

    } // startServiceRunOnce()


    /** Start a Timer which will run the given Service at regular intervals.
     * @param serviceName Name of the Service to run.
     * @param intervalMS Interval, in ms, at which to run the Service.
     */
    public void startTimedService( String serviceName, long intervalMS )
        throws IllegalStateException
    {
        if (halted) {
            throw new IllegalStateException("Client has been halted");
        }
        if ( timers.containsKey(serviceName) ) {
            throw new IllegalStateException( "Service '" + serviceName +
                                             "' has already started" );
        }
        Timer t = new Timer();
        t.scheduleAtFixedRate( new LocalTimerTask( serviceName ),
                               0, intervalMS );
        timers.put(serviceName, t);
        logger.info("Timed service '" + serviceName + "' started");
    
    } // startTimedService()


    // we can only stop a timed service, so...
    public void stopService( String serviceName ) {
        stopTimedService( serviceName );
    }

    /** Cancel a running timed Service. */
    public void stopTimedService( String serviceName ) {
        while ( ! allServicesStarted ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException x) { /*ignored*/ }
        }
        logger.info("Stopping service '" + serviceName + "'");
        Timer t = (Timer)timers.remove(serviceName);
        if (t!=null) {
            t.cancel();
        }
    }

    /** Associate this Client with the RemoteSystem which created it. */
    public void setParentSystem( RemoteSystem system ) {
        this.parent = system;
    }

    /** @return the RemoteSystem which created this Client. */
    public RemoteSystem getParentSystem() {
        return this.parent;
    }

    /** Associate a configuration object with this Client. */
    public void setConfiguration( SystemConfig config ) {
        this.config = config;

    } // setConfiguration()

    public SystemConfig getConfiguration() {
        return this.config;
    }

    /** Store the name by which this Client's parent knows it. */
    public void setName( String name ) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }


    /** Inner class for running timed services */
    class LocalTimerTask extends TimerTask {

        public String ttServiceName = null;

        public LocalTimerTask( String serviceName ) {
            this.ttServiceName = serviceName;
        }

        public void run() {
            Thread.currentThread().setName( ttServiceName );
            try {
                runService( ttServiceName );
            } catch ( Exception e ) {
                /* Keep retrying after failure: update service
                 * may yet need to be run (and it won't restart this
                 * service).  But log the failure...
                */
                logger.info( "Service '" + ttServiceName + 
                             "' failed to start" );
                logger.debug( "Service '" + ttServiceName +
                              "' failed to start", e );
                return;
            }
        }

    } // inner class LocalTimerTask


}

